﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Net.Sockets;
using System.IO;
using System.Web.Script.Serialization;
using System.Security.Cryptography;
using Et.Open.Socket.Demo.core;

namespace Et.Open.Socket.Demo
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        private static MD5 md5 = new MD5CryptoServiceProvider();

        private static readonly byte[] ProtocolHead = new byte[] { 0x02, 0x02, 0x02 };
        private static readonly byte[] ProtocolTail = new byte[] { 0x03, 0x03, 0x03 };

        public MainWindow()
        {
            InitializeComponent();
        }

        private void BtnStartServer_Click(object sender, RoutedEventArgs e)
        {
            TbServerLog.Clear();
            TcpServer.Configure(TbServerPid.Text, new DemoProcessor(this), int.Parse(TbServerPort.Text));
            if (TcpServer.Start())
            {
                AppendLog($"服务已启动，主机编号：{TcpServer.Pid}，端口：{TcpServer.Port}");
            }
            else
            {
                AppendLog("服务启动失败");
            }
        }

        private void BtnStopServer_Click(object sender, RoutedEventArgs e)
        {
            if (TcpServer.Stop())
            {
                AppendLog("服务已停止");
            }
            else
            {
                AppendLog("服务停止失败");
            }
        }

        public void AppendLog(string line)
        {
            Dispatcher.BeginInvoke((Action)(() => 
            {
                TbServerLog.AppendText(DateTime.Now.ToString("HH:mm:ss") + Environment.NewLine);
                TbServerLog.AppendText(line + Environment.NewLine);
            }));
        }

        private void BtnSendTare_Click(object sender, RoutedEventArgs e)
        {
            var data = new ApiData()
            {
                DataType = 1,
                Data = "{\"mTare\":" + TbClientTare.Text.Trim() + "}"
            };
            SendClientData(data);
        }

        private void BtnSaleDone_Click(object sender, RoutedEventArgs e)
        {
            var data = new ApiData()
            {
                DataType = 3,
                Data = "{\"mGr\":" + TbClientGross.Text.Trim() + "}"
            };
            SendClientData(data);
        }

        private void SendClientData(ApiData data)
        {
            try
            {
                var tosend = MakeApiFrame(data, TbClientPid.Text);
                DateTime begin = DateTime.Now;
                TcpClient c = new TcpClient(TbClientIp.Text, int.Parse(TbClientPort.Text));
                var strm = c.GetStream();
                strm.Write(tosend, 0, tosend.Length);
                byte[] headBuffer = new byte[5];
                strm.Read(headBuffer, 0, 5);
                ushort len = BitConverter.ToUInt16(headBuffer, 3);
                byte[] bodyBuffer = new byte[len];
                strm.Read(bodyBuffer, 0, bodyBuffer.Length);
                var ts = DateTime.Now - begin;
                strm.Close();
                c.Close();
                TbClientLog.Text =
                    "time: " + UnixTimeStampToDateTime(BitConverter.ToUInt64(bodyBuffer, 0)).ToString("yyyy-MM-dd HH:mm:ss")
                    + Environment.NewLine + "data type: " + BitConverter.ToUInt16(bodyBuffer, 8)
                    + Environment.NewLine + Encoding.UTF8.GetString(bodyBuffer, 10, len - 26)
                    + Environment.NewLine + "round trip time: " + ts.TotalMilliseconds;
            }
            catch(Exception ex)
            {
                TbClientLog.Text = ex.Message + Environment.NewLine + ex.StackTrace;
            }
        }

        private byte[] MakeApiFrame(ApiData data, string pid)
        {
            byte[] dataBuff = Encoding.UTF8.GetBytes(data.Data);
            ushort bodyLen = (ushort)(8 + 2 + dataBuff.Length + 16);
            byte[] rst = new byte[ProtocolHead.Length + 2 + bodyLen + ProtocolTail.Length];
            int idx = 0;
            Buffer.BlockCopy(ProtocolHead, 0, rst, 0, ProtocolHead.Length);
            idx += ProtocolHead.Length;
            Buffer.BlockCopy(BitConverter.GetBytes(bodyLen), 0, rst, idx, 2);
            idx += 2;
            Buffer.BlockCopy(BitConverter.GetBytes(DateTimeToUnixTimestamp(DateTime.Now)), 0, rst, idx, 8);
            idx += 8;
            Buffer.BlockCopy(BitConverter.GetBytes(data.DataType), 0, rst, idx, 2);
            idx += 2;
            Buffer.BlockCopy(dataBuff, 0, rst, idx, dataBuff.Length);
            idx += dataBuff.Length;
            byte[] toDigest = new byte[bodyLen - 16 + 5];
            Buffer.BlockCopy(rst, ProtocolHead.Length + 2, toDigest, 0, bodyLen - 16);
            Buffer.BlockCopy(Encoding.ASCII.GetBytes(pid), 0, toDigest, bodyLen - 16, 5);
            Buffer.BlockCopy(md5.ComputeHash(toDigest), 0, rst, idx, 16);
            idx += 16;
            Buffer.BlockCopy(ProtocolTail, 0, rst, idx, ProtocolTail.Length);
            return rst;
        }

        public static DateTime UnixTimeStampToDateTime(ulong unixTimeStamp)
        {
            // Unix timestamp is seconds past epoch
            DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
            dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
            return dtDateTime;
        }

        public static ulong DateTimeToUnixTimestamp(DateTime dateTime)
        {
            return (ulong)(TimeZoneInfo.ConvertTimeToUtc(dateTime) -
                   new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
        }
    }

    public class DemoProcessor : IApiProcessor
    {
        private JavaScriptSerializer ser = new JavaScriptSerializer();

        private MainWindow mwin;

        public DemoProcessor(MainWindow mainWindow)
        {
            this.mwin = mainWindow;
        }

        public ApiData Process(ApiData data)
        {
            mwin.AppendLog(string.Format("类型：{0}，数据：{1}", data.DataType, data.Data));
            return new ApiData()
            {
                DataType = (ushort)(data.DataType + 1),
                Data = data.DataType == 1 ?
                    ser.Serialize(new { errorCode = 0, errorMsg = "OK", uuid = Guid.NewGuid().ToString() }) :
                    ser.Serialize(new { errorCode = 0, errorMsg = "OK" })
            };
        }
    }
}
